#define __CL_ENABLE_EXCEPTIONS
#include <CL/cl.hpp>
#include <fstream>
#include <iostream>
#include <time.h>

using namespace std;

float * findProductOCL(float * matrix, float * v, int rows, int columns) {
	clock_t init, final;
	float * answer = new float[rows];

	try { 
        // Get available platforms
        vector<cl::Platform> platforms;
        cl::Platform::get(&platforms);
 
		string platformVendor;
		cout << "Nr of platforms: " << platforms.size() << endl;
		for(int i = 0; i < platforms.size() ; i++) {
			platforms[i].getInfo((cl_platform_info)CL_PLATFORM_VENDOR, &platformVendor);
			cout << "Platform "<< i << " is by: " << platformVendor << endl;
		}
		
        // Select the default platform and create a context using this platform
        cl_context_properties cps[3] = { 
            CL_CONTEXT_PLATFORM, 
            (cl_context_properties)(platforms[0])(), 
            0 
        };
		
        cl::Context context(CL_DEVICE_TYPE_ALL, cps);
 
        // Get a list of devices on this platform
        vector<cl::Device> devices = context.getInfo<CL_CONTEXT_DEVICES>();
		
		cout << "Nr of devices: " << devices.size() << endl;

		//for(int i = 0; i < devices.size() ; i++) {
		//	//device info
		//}

        // Create a command queue and choose a device (0=CPU, 1=GPU, need to make a check here)
        cl::CommandQueue queue = cl::CommandQueue(context, devices[1]);
 
        // Read source file
        ifstream sourceFile("basic_opencl.cl");
        string sourceCode(
            istreambuf_iterator<char>(sourceFile),
            (istreambuf_iterator<char>()));
        cl::Program::Sources source(1, make_pair(sourceCode.c_str(), sourceCode.length()+1));
 
        // Make program of the source code in the context
        cl::Program program = cl::Program(context, source);
 
        // Build program for these specific devices
        program.build(devices);
 
        // Make kernel
        cl::Kernel kernel(program, "basic_opencl");
		init=clock();
		cl::Buffer bufferMatrix = cl::Buffer(context, CL_MEM_READ_ONLY, rows * columns * sizeof(float));
		cl::Buffer bufferVector = cl::Buffer(context, CL_MEM_READ_ONLY, rows * sizeof(float));
		cl::Buffer bufferAnswer = cl::Buffer(context, CL_MEM_WRITE_ONLY, rows*sizeof(float));

		queue.enqueueWriteBuffer(bufferMatrix, CL_TRUE, 0, rows * columns * sizeof(float), matrix);		
        queue.enqueueWriteBuffer(bufferVector, CL_TRUE, 0, rows * sizeof(float), v);		

		kernel.setArg(0, bufferMatrix);
		kernel.setArg(1, bufferVector);
		kernel.setArg(2, rows);
		kernel.setArg(3, columns);
		kernel.setArg(4, bufferAnswer);

		cl::NDRange global(rows);
        cl::NDRange local(1);
		
        queue.enqueueNDRangeKernel(kernel, cl::NullRange, global, local);
		queue.enqueueReadBuffer(bufferAnswer, CL_TRUE, 0, rows * sizeof(float), answer);
		final=clock()-init;

    } catch(cl::Error error) {
       cout << error.what() << "(" << error.err() << ")" << endl;
    }
	cout << "Calculation took: " << (double)final / ((double)CLOCKS_PER_SEC) << " seconds. (Basic OpenCL, running kernel)" << endl;
	return answer;
}